/*
 	Script: GearsORM.Fields.js
 	        Contains the fields to be used in the models.
 	
 	License:
 	        MIT-style license.
*/

GearsORM.Field = {};
/*
 Class: GearsORM.Field.Primitive
 	a meta class for basic field types like varchar,integer,real and timestamp
 	
 Constructor:
 
 Parameters:
 	sqlType - the sql string for the type
 */
GearsORM.Field.Primitive = function(sqlType)
{
	var field = function(options)
	{
		this.options = options || {};
	};
	field.prototype = 
	{
		toSql:function()
		{
			var opts = this.options;
			return  sqlType +
			(opts.maxLength ? "(" + opts.maxLength + ") " : "")+ 
			(opts.notNull ? " NOT NULL " : "") +
			(opts.defaultValue ? " DEFAULT " + opts.defaultValue+" ": "") +
			(opts.unique ? " UNIQUE " : "");
		},
		isBackwardRelation:false,
		isRelation:false
	};
	field.constructor = GearsORM.Field.Primitive;
	return field;
};
/*
  a object holding all the field types(including relations)
 */
GearsORM.Fields =
{
	String:new GearsORM.Field.Primitive("VARCHAR"),
	Integer:new GearsORM.Field.Primitive("INTEGER"),
	Float:new GearsORM.Field.Primitive("REAL"),
	TimeStamp:new GearsORM.Field.Primitive("TIMESTAMP")
};

/*
 Class: GearsORM.Fields.ManyToOne
 
  represent a ManyToOne relation,this isn`t a actual table field.
  
 Constructor:
 
 Parameters:
 
  options - a object that look like this:
  {
   related:"name_of_the_related_table",
   onDeleteCascade:true/false - if true this will create triggers to emulate a ON DELETE CASCADE,if this is false it will do ON DELETE RESTRICT
  }
 */
GearsORM.Fields.ManyToOne = function(options)
{
	this.options = options;	
};

GearsORM.Fields.ManyToOne.prototype =
{
	/*
	 Function: copy
	  create a copy of this object
	 */
	copy:function()
	{
		return new GearsORM.Fields.ManyToOne(this.options); 
	},
	/*
	 Function: toSql
	  return the sql string for this field
	 */
	toSql:function()
	{
		return "";
	},
	/*
	 Function: select
	  do a SELECT on the related table
	  
	 Parameters:
	 	whereExpression - sql where expression
	 	params - a array of bind parameters to be used in sql query
	 */
	select:function(whereExpression,params)
	{
		return this._selectRemove(whereExpression,params,"select");
	},
	/*
	 Function: remove
	  do a DELETE on the related table
	  
	 Parameters:
	 	whereExpression - sql where expression
	 	params - a array of bind parameters to be used in sql query
	 */
	remove:function(whereExpression,params)
	{
		return this._selectRemove(whereExpression,params,"remove");
	},
	/*
	 Function: getRelatedClass
	  return the related model class
	 */
	getRelatedClass:function()
	{
		return GearsORM._models[this.options.related];
	},
	_selectRemove:function(whereExpression,params,method)
	{
		var fieldName = this.getRelatedClass() == this.modelClass ? this.options.related + "_id": "rowid";
		params = params || [];
		params.unshift(this.instance.rowid);
		return this.getRelatedClass()[method](this.options.related+"."+fieldName+" = ? "+(whereExpression ? " AND " + whereExpression : "") ,params);
	},
	isBackwardRelation:true,
	isRelation:true
};


/*
 Class: GearsORM.Fields.OneToMany
  represent a forigen key relation
  
 Constructor:
 
 Parameters:
 
  options - a object that look like this:
  {
   related:"name_of_the_related_table",
   onDeleteCascade:if true this will create triggers to emulate a ON DELETE CASCADE,if this is false it will do ON DELETE RESTRICT,
   allowNull:allow a null value
  }
 */
GearsORM.Fields.OneToMany = function(options)
{
	this.options = options;
};

GearsORM.Fields.OneToMany.prototype =
{
	/*
	 Function: copy
	  create a copy of this object
	 */
	copy:function()
	{
		return new GearsORM.Fields.OneToMany(this.options); 
	},
	/*
	 Function: toSql
	  return the sql string for this field
	 */
	toSql:function()
	{
		return GearsORM.Sql.oneToManySql(this.options.related,this.options.allowNull);
	},
	/*
	 Function: getRelatedClass
	  return the related model class
	 */
	getRelatedClass:function()
	{
		return GearsORM._models[this.options.related];
	},
	isBackwardRelation:false,
	isRelation:true
};
/*
 Class: GearsORM.Fields.ManyToMany
  represent a many to many relation between two tables using a many to many relation table
  
 Constructor:
 
 Parameters:
 
  options - a object that look like this:
  {
   related:"name_of_the_related_table",
   onDeleteCascade:if true this will create triggers to emulate a ON DELETE CASCADE,if this is false it will do ON DELETE RESTRICT
  }
 */
GearsORM.Fields.ManyToMany = function(options)
{
	this.options = options;
};

GearsORM.Fields.ManyToMany.prototype =
{
	_m2mModel:null,
	/*
	 Function: copy
	  create a copy of this object
	 */
	copy:function()
	{
		return new GearsORM.Fields.ManyToMany(this.options); 
	},
	/*
	 Function: getRelatedClass
	  return the related model class
	 */
	getRelationModel:function()
	{
		if(!this._m2mModel)
		{
			var names = [this.modelClass.options.name,this.options.related].sort();
			var thisModelName = names[0];
			var relatedModelName = names[1];
			
			var fields = {};
			fields[relatedModelName] = new GearsORM.Fields.OneToMany({related:relatedModelName,onDeleteCascade:true});
			fields[thisModelName] = new GearsORM.Fields.OneToMany({related:thisModelName,onDeleteCascade:true});
			
			this._m2mModel = new GearsORM.Model({
				name:"m2m_"+ thisModelName + "_"+ relatedModelName,
				fields:fields
			});
		}
		return this._m2mModel;
	},
	/*
	 Function: add
	 	add a relation between this object to another object
	 
	 Parameters:
	 	realtedObject - a related model instance or a id of that object
	 */
	add:function(realtedObject)
	{
		var m2mTable = this.getRelationModel();
		var relation = new m2mTable();
		relation[this.modelClass.options.name] = this.instance;
		relation[this.getRelatedClass().options.name] = realtedObject;
		relation.save();
		return relation;
	},
	/*
	 Function: toSql
	 	return the sql string for this field
	 */
	toSql:function()
	{
		return "";
	},
	/*
	 Function: createTable
	 	create the many to many table
	 */
	createTable:function()
	{
		//create a new table for the many-to-many relation only if related class is defined
		if(GearsORM._models[this.options.related])
		{
			this.getRelationModel().createTable();
		}
	},
	/*
	 Function: select
	 	do a SELECT on the related table
	 	
	 Parameters:
	 	whereExpression - sql where expression.
	 	params - a array of bind parameters to be used in sql query.
	 */
	select:function(whereExpression,params)
	{
		var m2mTableName = this.getRelationModel().options.name;
		var relatedClass = this.getRelatedClass();
		return new GearsORM.ResultIterator(GearsORM.execute(GearsORM.Sql.selectWithManyToMany(this.modelClass,relatedClass,m2mTableName,whereExpression,this.instance.rowid),params),relatedClass);
	},
	/*
	 Function: remove
	 	delete a relation between this object and other object
	 	
	 Parameters:
	 	realtedObject - a related model instance or a id of that object
	 */
	remove:function(related)
	{
		var relatedName = this.getRelatedClass().options.name; 
		return this.getRelationModel().remove(relatedName + "_id = ?",[typeof(related) == "number" ? related : related.rowid]);
	},
	/*
	 Function: dropTable
	  drop the many to many table
	 */
	dropTable:function()
	{
		this.getRelationModel().dropTable();
	},
	/*
	 Function: getRelatedClass
	  return the related model class
	 */
	getRelatedClass:function()
	{
		return GearsORM._models[this.options.related];
	},
	isBackwardRelation:true,
	isRelation:true
};